package org.webdb.jdbc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.webdb.core.ThreadContext;
import org.webdb.jdbc.impl.JDBCProviderImpl;

public class JDBCFactory {
    private static final String DEFAULT_DBMS = "*";
    private static final Map<String, JDBCSqlFactory> supported = new HashMap<String, JDBCSqlFactory>();

    static {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null)
            cl = JDBCFactory.class.getClassLoader();

        String resourceName;
        InputStream in = cl
                .getResourceAsStream(resourceName = "META-INF/services/"
                        + JDBCSqlFactory.class.getName());
        if (in == null)
            throw new JDBCFactoryError("No ".concat(resourceName));

        BufferedReader reader = new BufferedReader(new InputStreamReader(in));

        try {
            while (reader.ready()) {
                String line = reader.readLine();
                try {
                    Class<?> clazz = cl.loadClass(line);
                    if (JDBCSqlFactory.class.isAssignableFrom(clazz)) {
                        JDBCSqlFactory jdbcSqlProvider = (JDBCSqlFactory) clazz
                                .newInstance();
                        String[] dbms = jdbcSqlProvider.getSupported();
                        for (String db : dbms) {
                            db = db.toLowerCase();
                            JDBCSqlFactory old = supported.put(db,
                                    jdbcSqlProvider);
                            if (old != null) {
                                System.err.println("JDBCFactory - Conflict: "
                                        + db + "=" + old.getClass().getName()
                                        + " -> " + db + "=" + clazz.getName());
                            }
                        }
                    } else
                        System.err.println("JDBCFactory - " + clazz.getName()
                                + " isn't a valid JDBCSqlProvider!");
                } catch (Exception e) {
                    System.err.println("JDBCFactory - Error in ".concat(line));
                    e.printStackTrace(System.err);
                }
            }
        } catch (IOException e) {
            throw new JDBCFactoryError("Error in ".concat(resourceName), e);
        }
    }
    private DataSource dataSource;

    private final ThreadLocal<JDBCProvider> jdbcProviders = new ThreadLocal<JDBCProvider>();

    private String name;
    private JDBCSqlFactory sqlFactory;
    private final ThreadLocal<JDBCSqlProvider> sqlProviders = new ThreadLocal<JDBCSqlProvider>();

    public JDBCFactory(JDBCMetaData metaData, DataSource dataSource) {
        this.dataSource = dataSource;
        this.sqlFactory = findSqlFactory(metaData);
        this.name = metaData.getName();
    }

    private JDBCSqlFactory findSqlFactory(JDBCMetaData metaData) {
        String dbms = metaData.getDBMSName().toLowerCase();
        JDBCSqlFactory factory = supported.get(dbms);

        if (factory == null)
            factory = supported.get(DEFAULT_DBMS);

        if (factory == null)
            throw new JDBCException("No support to " + metaData.getDBMSName());

        return factory;
    }

    public JDBCProvider getJDBCProvider() {
        JDBCProvider jdbcProvider = jdbcProviders.get();
        if (jdbcProvider == null) {
            try {
                jdbcProvider = new JDBCProviderImpl(name,
                        dataSource.getConnection());
                jdbcProviders.set(jdbcProvider);
                ThreadContext.put(jdbcProvider);
            } catch (Exception e) {
                throw new JDBCException(toString() + " Getting JDBCProvider", e);
            }
        }
        return jdbcProvider;
    }

    public JDBCSqlProvider getSqlProvider() {
        JDBCSqlProvider sqlProvider = sqlProviders.get();
        if (sqlProvider == null) {
            synchronized (this) {
                if ((sqlProvider = sqlProviders.get()) == null) {
                    try {
                        sqlProvider = sqlFactory.createProvider(name);
                    } catch (Exception e) {
                        throw new JDBCException(toString()
                                + " Creating provider!", e);
                    }
                }
            }
        }
        return sqlProvider;
    }

    @Override
    public String toString() {
        return JDBCFactory.class.getName() + "[" + name + "]";
    }
}
